package xml;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Xml {
	//
	// Xml class is used for parsing objects to and from Xml format text files
	//
	protected static String	startOpenBrace		= "<";
	protected static String	startCloseBrace		= ">";
	protected static String	endingOpenBrace		= "</";
	protected static String	endingCloseBrace	= ">";
	protected static String	nullString			= "null";

	protected static final String SOB = startOpenBrace;
	protected static final String SCB = startCloseBrace;
	protected static final String EOB = endingOpenBrace;
	protected static final String ECB = endingCloseBrace;

	private static boolean	printNice = true;

	protected static synchronized final String toStr(Object obj) {
		if (obj==null) {
			return getNullString();
		} else {
			return obj.toString();
		}
	}

	protected static synchronized final String getBetweenIncluding(String open, String close, String xml) {
		//
		// If there is a matching open and close tag, this will return the string that
		// including the open and close tags
		// It will return null if it's not present
		//
		int openIdx = findOpenStr(open, xml);
		int closeIdx = findCloseStr(open,close,xml);
		if (openIdx<0 || closeIdx<0) {
			return null;
		} else {
			return xml.substring(openIdx, closeIdx+close.length());
		}
	}
	protected static synchronized final String getBetweenExcluding(String open, String close, String xml) {
		String result = getBetweenIncluding(open,close,xml);
		if (result != null) {
			if (open.length()+close.length()>=result.length()) {
				return "";
			} else {
				return result.substring(open.length(), result.length() - close.length());
			}
		} else {
			return null;
		}
	}

	public static synchronized final String getXml(String key, String xml) 				{	return getBetweenIncluding(startTag(key),endTag(key),xml);			}
	public static synchronized final String getTag(String key, String xml)				{	return getBetweenExcluding(startTag(key),endTag(key),xml);			}

	protected static synchronized final int	find(String text, String xml) {
		if (xml==null || text==null) return -1;
		return xml.indexOf(text);
	}
	protected static synchronized final int	findOpenStr(String open, String xml)	{
		return find(open,xml);
	}
	protected static synchronized final int	findCloseStr(String open, String close, String xml) {
		//
		// For each open there is a matching close brace
		// Returns -1 if it's not present. It is assumed that
		// there is an open tage present in the xml
		//
		int openIdx;
		int closeIdx;
		int	i, n, match;
		String	S;
		i = 0;
		n = 0;
		match = -1;
		while(i < xml.length()) {
			S = xml.substring(i, xml.length());
			openIdx		= find(open,S);
			closeIdx	= find(close,S);

			if (openIdx<0 && closeIdx<0) {
				if (n>0) return -1;
			} else if (closeIdx < 0 || ((openIdx >= 0) && (openIdx < closeIdx))) {
				n++;
				i += openIdx + open.length();
			} else {
				if (n>0) {
					n--;
					match = i + closeIdx;
					if (n==0) {
						return match;
					}
				} else {
				}
				i += closeIdx + close.length();
			}
		}
		return -1;
	}

	public static synchronized final	int		findStartTag(String key, String xml)	{	return findOpenStr(startTag(key),	xml);							}
	public static synchronized final	int		findEndTag(String key, String xml)		{	return findCloseStr(startTag(key),	endTag(key),	xml);			}

	//
	// These methods are used for printing out the xml in a nice and neat format
	//
	private static int		numTabs = 0;
	private static String	tabString = "  ";
	private static String	newLine = "\n";

	protected static synchronized final String getIndent() {	return getIndent(numTabs);		}
	protected static synchronized final String getIndent(int numTabs) {
		int i = 0;
		String	indent = "";
		for(i=0; i<numTabs; i++) {
			indent += getTabString();
		}
		return indent;
	}

	public static final	synchronized	String	insideTag(String tag) {
		String inside = "";
		if (isPrintNice()) {
			inside += tag;
		} else {
			inside = tag;
		}
		return inside;
	}
	public static final synchronized	String	startTag(String tag) {
		String start = "";
		if (isPrintNice()) {
			start += getNewLine() + getIndent();
			start += getStartOpenBrace() + tag + getStartCloseBrace();
			numTabs++;
		} else {
			start = getStartOpenBrace() + tag + getStartCloseBrace();
		}
		return start;
	}
	public static final synchronized	String	endTag(String tag) {
		String end = "";
		if (isPrintNice()) {
			numTabs--;
			end += getNewLine() + getIndent();
			end += getEndingOpenBrace() + tag + getEndingCloseBrace();
		} else {
			end = getEndingOpenBrace() + tag + getEndingCloseBrace();
		}

		return end;
	}

	public static synchronized final	String	tagFormat(String start, String inside, String end) {
		return tagFormat(start,inside,end,isPrintNice(),numTabs);
	}
	public static synchronized final	String	tagFormat(String start, String inside, String end, boolean printNice, int rootTab) {
		String xml = "";
		if (printNice) {
			xml += start;
			xml += inside;
			xml += end;
		} else {
			xml = start + inside + end;
		}
		return xml;
	}

	public static synchronized final 	String	write(String tag, String data)			{	return tagFormat( startTag(tag) , insideTag(data)	 					, endTag(tag) );		}
	public static synchronized final	String	write(String tag, int data)				{	return tagFormat( startTag(tag) , insideTag(Integer.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, byte data)			{	return tagFormat( startTag(tag) , insideTag(Byte.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, short data)			{	return tagFormat( startTag(tag) , insideTag(Short.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, char data)			{	return tagFormat( startTag(tag) , insideTag(Character.toString(data))	, endTag(tag) );		}
	public static synchronized final	String	write(String tag, float data)			{	return tagFormat( startTag(tag) , insideTag(Float.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, double data)			{	return tagFormat( startTag(tag) , insideTag(Double.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, long data)			{	return tagFormat( startTag(tag) , insideTag(Long.toString(data))		, endTag(tag) );		}
	public static synchronized final	String	write(String tag, boolean data)			{	return tagFormat( startTag(tag) , insideTag(Boolean.toString(data))		, endTag(tag) );		}

	public static synchronized final	String	write(String tag, Integer data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Byte data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Short data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Character data)		{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Float data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Double data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Long data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}
	public static synchronized final	String	write(String tag, Boolean data)			{	return tagFormat( startTag(tag) , insideTag(toStr(data))				, endTag(tag) );		}

	public static synchronized final String	write(String tag, Object obj) {
		//
		// This is the key method that will generate xml for an object
		//

		if (tag == null)	tag = getNullString();
		if (obj==null)		return write(tag,getNullString());

		String		xml = "";
		Class<?>	oClass = obj.getClass();
		int i, numFields;
		String		body = "";
		Field		F;
		String		fname;
		String		fxml;


		numFields = oClass.getFields().length;
		numTabs++;

		if 			(obj.getClass().equals(String.class))		{	body = toStr(obj);		}
		else	if 	(obj.getClass().isPrimitive())				{	body = toStr(obj);		}
		else	if 	(obj.getClass().equals(Integer.class))		{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Byte.class))			{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Short.class))		{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Character.class))	{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Float.class))		{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Double.class))		{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Long.class))			{	body = toStr(obj);		}
		else	if	(obj.getClass().equals(Boolean.class))		{	body = toStr(obj);		}
		else {

			body += write("numFields",Integer.toString(numFields));
			for(i=0; i<numFields; i++) {
				F = oClass.getFields()[i];
				fname = F.getName();

				try {
					if 			(F.getClass().equals(String.class))		{	fxml = write(tag,toStr(obj));		}
					else	if 	(F.getClass().isPrimitive())			{	fxml = write(tag,toStr(obj));		}
					else	if 	(F.getClass().equals(Integer.class))	{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Byte.class))		{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Short.class))		{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Character.class))	{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Float.class))		{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Double.class))		{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Long.class))		{	fxml = write(tag,toStr(obj));		}
					else	if	(F.getClass().equals(Boolean.class))	{	fxml = write(tag,toStr(obj));		}
					else												{
						fxml = write(fname, F.get(obj));
					}
				} catch (IllegalArgumentException e) {
					fxml = write(fname, "Illegal Argument");
				} catch (IllegalAccessException e) {
					fxml = write(fname, "Illegal Access");
				}
				body += fxml;
			}
		}

		numTabs--;
		xml = write(tag,body);
		return xml;
	}

	public static synchronized final byte		readByte(String data)		{	return Byte.parseByte(data);		}
	public static synchronized final Short		readShort(String data)		{	return Short.parseShort(data);		}
	public static synchronized final int		readInt(String data)		{	return Integer.parseInt(data);		}
	public static synchronized final char		readChar(String data)		{	return data.charAt(0);				}
	public static synchronized final long		readLong(String data)		{	return Long.parseLong(data);		}
	public static synchronized final float		readFloat(String data)		{	return Float.parseFloat(data);		}
	public static synchronized final double		readDouble(String data)		{	return Double.parseDouble(data);	}
	public static synchronized final boolean	readBoolean(String data)	{	return Boolean.parseBoolean(data);	}


	public static synchronized final Object	readObj(Class<? extends Object> oClass, String data) throws InstantiationException, IllegalAccessException {
		//
		// This method will attempt to read in all the values of the fields from xml.
		// This method is recursive and cannot allocate new memory where pointers are suppossed to be.
		// This method assumes that all memory required to hold all the primitives has
		//   already been allocated
		//

		if (oClass==null || data==null || data.compareTo(getNullString())==0) {
			return null;
		}


		int 		i, numFields;
		Field		F;
		Class<?>	fclass;
		String		fname;
		Object 		obj = null;

		if 		(oClass.equals(Byte.class))					{	obj = new Byte(data);							}
		else if	(oClass.equals(Short.class))				{	obj = new Short(data);							}
		else if	(oClass.equals(Character.class))			{	obj = new Character(data.charAt(0));			}
		else if	(oClass.equals(Integer.class))				{	obj = new Integer(data);						}
		else if	(oClass.equals(Long.class))					{	obj = new Long(data);							}
		else if	(oClass.equals(Float.class))				{	obj = new Float(data);							}
		else if	(oClass.equals(Double.class))				{	obj = new Double(data);							}
		else if	(oClass.equals(Boolean.class))				{	obj = new Boolean(data);						}
		else {
			try {
				obj = oClass.newInstance();
			} catch (InstantiationException e) {
				System.out.println("Could not instantiate a member of class " + oClass.getCanonicalName());
				e.printStackTrace();
				throw e;
			} catch (IllegalAccessException e) {
				System.out.println("Could not access a member of class " + oClass.getCanonicalName());
				e.printStackTrace();
				throw e;
			}
			String		body;
			numFields = oClass.getFields().length;

			for(i=0; i<numFields; i++) {
				F = oClass.getFields()[i];
				fclass = F.getClass();
				fname = F.getName();
				body = Xml.getTag(fname, data);
				if (fclass.equals(byte.class))				{	F.set(obj,readByte(body));		}
				else if (fclass.equals(short.class))		{	F.set(obj,readShort(body));		}
				else if (fclass.equals(char.class))			{	F.set(obj,readChar(body));		}
				else if (fclass.equals(int.class))			{	F.set(obj,readInt(body));		}
				else if (fclass.equals(long.class))			{	F.set(obj,readLong(body));		}
				else if (fclass.equals(float.class))		{	F.set(obj,readFloat(body));		}
				else if (fclass.equals(double.class))		{	F.set(obj,readDouble(body));	}
				else if (fclass.equals(boolean.class))		{	F.set(obj,readBoolean(body));	}
				else {
					F.set(obj,readObj(F.get(obj).getClass(), body));
				}
			}
		}
		return obj;
	}

	public static synchronized final String	getFirstStr(String xml)	{
		//
		// This method will return the name of the first key it finds
		//
		return getBetweenExcluding(getStartOpenBrace(), getStartCloseBrace(), xml);
	}
	public static synchronized final String getFirstTag(String xml) {
		//
		// This will return the contents of the first tag it finds excluding the open/close tags
		//
		String	firstTag = getFirstStr(xml);
		if (firstTag!=null) {
			return getTag(firstTag, xml);
		} else {
			return null;
		}
	}
	public static synchronized final String getFirstXml(String xml) {
		//
		// This will return the contents of the first tag it finds surrounded bu those tags
		//
		String	firstTag = getFirstStr(xml);
		if (firstTag!=null) {
			return getXml(firstTag, xml);
		} else {
			return null;
		}
	}

	//
	// Getters and Setters for the braces used
	//

	public static synchronized final void setStartOpenBrace(String startOpenBrace)		{	Xml.startOpenBrace = startOpenBrace;		}
	public static synchronized final void setStartCloseBrace(String startCloseBrace)	{	Xml.startCloseBrace = startCloseBrace;		}
	public static synchronized final void setEndingOpenBrace(String endingOpenBrace)	{	Xml.endingOpenBrace = endingOpenBrace;		}
	public static synchronized final void setEndingCloseBrace(String endingCloseBrace)	{	Xml.endingCloseBrace = endingCloseBrace;	}

	public static synchronized final String getStartOpenBrace()							{	return startOpenBrace;						}
	public static synchronized final String getStartCloseBrace()						{	return startCloseBrace;						}
	public static synchronized final String getEndingOpenBrace()						{	return endingOpenBrace;						}
	public static synchronized final String getEndingCloseBrace()						{	return endingCloseBrace;					}

	public static synchronized final boolean isPrintNice() 								{	return printNice;							}
	public static synchronized final String getTabString() 								{	return tabString;							}
	public static synchronized final String getNewLine()								{	return newLine;								}
	public static synchronized final String getNullString()								{	return nullString;							}

	public static synchronized final void setPrintNice(boolean value)					{	printNice = value;							}
	public static synchronized final void setTabString(String tabString)				{	Xml.tabString = tabString;					}
	public static synchronized final void setNewLine(String newLine)					{	Xml.newLine = newLine;						}
	public static synchronized final void setNullString(String nullString)				{	Xml.nullString = nullString;				}

	public static synchronized final String removeTag(String tag, String input) {
		String	output = input;
		String	toRemove = Xml.getXml(tag,input);

		if (toRemove!=null && toRemove.length() > 0) {
			int		index = input.indexOf(toRemove);
			output = input.substring(0, index) + input.substring(index+toRemove.length());
		}
		return output;
	}

	public static synchronized final Map<String,String>	getTags(String inputXml) {
		//
		// This method will return the next level of tags from an xml stream
		//
		Map<String,String>	xmlTagMap = new HashMap<String, String>();

		boolean done = false;
		String	tag, tagXml, tagBody;
		while(!done) {
			tag = Xml.getFirstTag(inputXml);
			if (tag!=null && tag.length()>0) {
				tagXml = Xml.getXml(tag, inputXml);
				tagBody = Xml.getTag(tag, tagXml);
				xmlTagMap.put(tagXml, tagBody);
				Xml.removeTag(tag, inputXml);
			} else {
				done = true;
			}
		}
		return xmlTagMap;
	}

}
